//
// Created by tacom on 22-9-6.
//

#include "hilditch.h"

pybind11::array_t<unsigned char> hilditch::hilditchStart(cv::Mat &input) {
    // get input shape
    cv::Mat inputChannel[3];
    cv::split(input, inputChannel);
    int H = input.rows, W = input.cols, C = input.channels();

    // prepare out image
    cv::Mat out;
    cv::threshold(inputChannel[0], out, 0, 1, cv::THRESH_BINARY_INV);

    // scan
    while (true){
        std::vector<cv::Point> s1;
        std::vector<cv::Point> s2;

        // step 1: rasta scan
        for(int y = 1; y < H - 1; ++y){
            for(int x = 1; x < W - 1; ++x){

                // condition 1
                if(out.at<u_char>(y, x) > 0)
                    continue;

                // condition2
                int f1 = 0;
                if ((out.at<u_char>(y - 1, x + 1) - out.at<u_char>(y - 1, x)) == 1)
                    ++f1;
                if ((out.at<u_char>(y, x + 1) - out.at<u_char>(y - 1, x + 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y + 1, x + 1) - out.at<u_char>(y, x + 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y + 1, x) - out.at<u_char>(y + 1, x + 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y + 1, x - 1) - out.at<u_char>(y + 1, x)) == 1)
                    ++f1;
                if ((out.at<u_char>(y, x - 1) - out.at<u_char>(y + 1, x - 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y - 1, x - 1) - out.at<u_char>(y, x - 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y - 1, x) - out.at<u_char>(y - 1, x - 1)) == 1)
                    ++f1;
                if (f1 != 1)
                    continue;

                // condition 3
                cv::Scalar f2 = cv::sum(out(cv::Range(y - 1, y + 2), cv::Range(x - 1, x + 2)));
                if (f2[0] < 2 || f2[0] > 6)
                    continue;

                // condition 4
                if((out.at<u_char>(y - 1, x) + out.at<u_char>(y, x + 1) + out.at<u_char>(y + 1, x)) < 1)
                    continue;

                // condition 5
                if((out.at<u_char>(y, x + 1) + out.at<u_char>(y + 1, x) + out.at<u_char>(y, x - 1)) < 1)
                    continue;

                s1.push_back(cv::Point(x, y));
            }
        }

        for (cv::Point eachPoint : s1){
            out.at<u_char>(eachPoint) = 1;
        }

        // step 2
        for(int y = 1; y < H - 1; ++y){
            for(int x = 1; x < W - 1; ++x){

                // condition 1
                if(out.at<u_char>(y, x) > 0)
                    continue;

                // condition2
                int f1 = 0;
                if ((out.at<u_char>(y - 1, x + 1) - out.at<u_char>(y - 1, x)) == 1)
                    ++f1;
                if ((out.at<u_char>(y, x + 1) - out.at<u_char>(y - 1, x + 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y + 1, x + 1) - out.at<u_char>(y, x + 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y + 1, x) - out.at<u_char>(y + 1, x + 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y + 1, x - 1) - out.at<u_char>(y + 1, x)) == 1)
                    ++f1;
                if ((out.at<u_char>(y, x - 1) - out.at<u_char>(y + 1, x - 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y - 1, x - 1) - out.at<u_char>(y, x - 1)) == 1)
                    ++f1;
                if ((out.at<u_char>(y - 1, x) - out.at<u_char>(y - 1, x - 1)) == 1)
                    ++f1;
                if (f1 != 1)
                    continue;

                // condition 3
                cv::Scalar f2 = cv::sum(out(cv::Range(y - 1, y + 2), cv::Range(x - 1, x + 2)));
                if (f2[0] < 2 || f2[0] > 6)
                    continue;

                // condition 4
                if((out.at<u_char>(y - 1, x) + out.at<u_char>(y, x + 1) + out.at<u_char>(y + 1, x)) < 1)
                    continue;

                // condition 5
                if((out.at<u_char>(y, x + 1) + out.at<u_char>(y + 1, x) + out.at<u_char>(y, x - 1)) < 1)
                    continue;

                s2.push_back(cv::Point(x, y));
            }
        }

        for (cv::Point eachPoint : s2){
            out.at<u_char>(eachPoint) = 1;
        }

        // if not any pixel is changed
        if (s1.size() < 1 && s2.size() < 1)
            break;
    }

    // output
    cv::Mat outInverse = (1 - out) * 255;

    return NumPyMatConvert::Mat8UC1ToNumpy(outInverse);
}
